package com.szlanyou.protocol_service;

import android.text.TextUtils;

import com.szlanyou.protocol_base.BaseProtocolHandler;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author 陈章
 * create at 2021/1/19 16:37
 * desc:解析一帧数据
 * 数据传输格式
 *      #{命令字:数据单元}#
 *
 * ############################################
 * 其实任何形式的协议，都可以按照这个模板处理。
 * ############################################
 *
 * 注意：
 * 1)已经确定了服务端与客户端所有的字符集都是使用的UTF-8，传递中文有时会出现乱码�。所以定协议时不要使用中文字符，最好采用十六进制的字符串。
 */
public class ProtocolHandler extends BaseProtocolHandler {
    private static final String TAG = "ProtocolHandler";
    private byte[] bytesReadBuf = new byte[1024];
    private byte[] frameDatas = null;
    private String aFrameData = "";
    private volatile StringBuilder bufferReadMsg = new StringBuilder();
    private Object lock = this;
    private boolean keepRun = true;

    /**
     * 标记是否启动过
     */
    private AtomicBoolean started = new AtomicBoolean(false);

    public ProtocolHandler() {
        bufferReadMsg.setLength(0);
    }

    public void start(){
        if(started.compareAndSet(false, true)){
            new FrameDecodeThread().start();
        }
    }

    /**
     * 整帧协议解析线程,采用生产者-消费者模型。
     * 原来的设计是：从流里read到数据，解析一帧。这样的话，由于socket连包问题，会导致取帧延迟，甚至缓冲区里的帧最后有没有取出来的。
     * 现在的设计是：消费者随时都在解析帧，缓冲区没有数据时，消费者等待。缓冲区有数据了，消费者唤醒。
     */
    private class FrameDecodeThread extends Thread{
        @Override
        public void run() {
            LogUtil.d(TAG,"整帧协议解析线程启动");
            while (keepRun){
                /**
                 * 为了保证bufferReadMsg的线程安全及让线程等待缓冲区，避免空转，加锁。
                 */
                synchronized (lock){
                    String bufMsg = bufferReadMsg.toString();
                    if(bufMsg.isEmpty()){
                        try {
                            LogUtil.d("缓冲区为空，等待...");
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }



                    //首部
                    int headIndex;
                    //清空脏数据
                    do{
                        //这里要及时更新缓冲区，因为wait之后，1）缓冲区更新了最后一次缓冲 2）清除脏数据，缓冲区也会变化。
                        bufMsg = bufferReadMsg.toString();

                        headIndex = bufMsg.indexOf(ProtocolUtil.DATA_HEAD);
                        if(-1 == headIndex && !bufMsg.isEmpty()){
                            LogUtil.e(TAG, "step1_get_wholeFrame: 非#{打头的数据，清空脏数据。当前缓冲区：\n" + bufMsg);
                            bufferReadMsg.delete(0,1);
                        }else{
                            break;
                        }
                    }while (headIndex == -1 && keepRun);

                    int footIndex = bufMsg.indexOf(ProtocolUtil.DATA_END);
                    //完整数据还没有取完
                    if(footIndex == -1){
                        try {
                            LogUtil.d("帧尾未找到，等待...,当前缓冲区：\n" + bufMsg);
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }else{
                        String oneFrameData = bufMsg.substring(headIndex, footIndex+2);
                        findOneFrame(oneFrameData);
                        //注意将取的整帧数据清掉
                        if(oneFrameData.equals(bufMsg)){
                            bufMsg = "";
                        }else{
                            bufMsg = bufMsg.replace(oneFrameData,"");
                        }
                        bufferReadMsg.setLength(0);
                        bufferReadMsg.append(bufMsg);
                    }
                }
            }
        }
    }

    @Override
    public synchronized void handle(InputStream in) {
        try {
            int nLen = in.read(bytesReadBuf);
            if (nLen != -1) {
                frameDatas = new byte[nLen];
                System.arraycopy(bytesReadBuf, 0, frameDatas, 0, nLen);
                aFrameData += new String(frameDatas,java.nio.charset.StandardCharsets.UTF_8);
                LogUtil.d(TAG, "接收<< " + aFrameData);
                //在这里做分包判断
                if (!TextUtils.isEmpty(aFrameData)) {
                    put2Buf(aFrameData);
                    aFrameData = "";
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            if(onDecodeListener != null){
                onDecodeListener.onReadException(e);
            }
        }
    }

    @Override
    public void handle(byte[] in) {
        int nLen = in.length;
        if (nLen != -1) {
            aFrameData += new String(in,java.nio.charset.StandardCharsets.UTF_8);
            LogUtil.d(TAG, "接收<< " + aFrameData);
            //在这里做分包判断
            if (!TextUtils.isEmpty(aFrameData)) {
                put2Buf(aFrameData);
                aFrameData = "";
            }
        }
    }

    /**
     * 将数据加入缓冲区
     * @param str
     */
    private void put2Buf(String str) {
        /**
         * 为了保证bufferReadMsg的线程安全，加锁。
         */
        LogUtil.d("准备将数据注入缓冲区");
        synchronized (lock){
            bufferReadMsg.append(str);
            LogUtil.d("激活缓冲区");
            lock.notify();
        }
    }


    /**
     * 解析出一帧数据
     * @param wholeData
     */
    private void findOneFrame(String wholeData){
        LogUtil.d(TAG, "findOneFrame: " + wholeData);
        String realMsg = wholeData.substring(2, wholeData.length() - 2);
        if(onDecodeListener != null){
            onDecodeListener.onDecoded(realMsg);
        }
    }

    /**
     * 停止线程，释放资源。
     */
    public void release(){
        keepRun = true;
        started.set(false);
    }
}
